﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Rage.Base.Abilities {
    public class GenericRotation : Rotation
    {
        public GenericRotation(CollectedCharacter character, float latency, float allowedReact)
        {
            CollChar = character;

            _cachedLatentGCD = 1.5f + latency + allowedReact;
            AbilityWrapper.LatentGCD = _cachedLatentGCD;
            _cachedNumGCDsO20 = FightDurationO20 / LatentGCD;
            _cachedNumGCDsU20 = FightDurationU20 / LatentGCD;
        }

        private List<Type> _startingActionsOrder;
        private List<Type> _rotationOrderO20;
        private List<Type> _rotationOrderU20;
        public List<Type> StartingActionsOrder { get { return _startingActionsOrder ?? (_startingActionsOrder = new List<Type>()); } set { _startingActionsOrder = value; } }
        public  List<Type>  RotationOrderO20 { get { return _rotationOrderO20 ?? (_rotationOrderO20 = new List<Type>()); } set { _rotationOrderO20 = value; } }
        public  List<Type>  RotationOrderU20 { get { return _rotationOrderU20 ?? (_rotationOrderU20 = new List<Type>()); } set { _rotationOrderU20 = value; } }
        public Type FillerMoveO20 { get; set; }
        public Type FillerMoveU20 { get; set; }

        public void SetRotationAbilities(List<Type> rotation, Type filler) { SetRotationAbilities(null, rotation, filler, null, null); }
        public void SetRotationAbilities(List<Type> rotationO20, Type fillerO20, List<Type> rotationU20, Type fillerU20) { SetRotationAbilities(null, rotationO20, fillerO20, rotationU20, fillerU20); }
        public void SetRotationAbilities(List<Type> rotationOpener, List<Type> rotation, Type filler) { SetRotationAbilities(rotationOpener, rotation, filler, null, null); }
        public void SetRotationAbilities(List<Type> rotationOpener, List<Type> rotationO20, Type fillerO20, List<Type> rotationU20, Type fillerU20)
        {
            StartingActionsOrder = rotationOpener;
            RotationOrderO20 = rotationO20;
            FillerMoveO20 = fillerO20;
            RotationOrderU20 = rotationU20;
            FillerMoveU20 = fillerU20;
        }

        protected override void InitAbilities()
        {
            try
            {
                InvalidateAbilityLists();
                //
                if (StartingActionsOrder != null) { doList(StartingActionsOrder); }
                if (RotationOrderO20 != null) { doList(RotationOrderO20); }
                if (RotationOrderU20 != null) { doList(RotationOrderU20); }
                if (FillerMoveO20 != null) { doSingle(FillerMoveO20); }
                if (FillerMoveU20 != null) { doSingle(FillerMoveU20); }
            }
            catch (Exception ex)
            {
                new Base.ErrorBox()
                {
                    Title = "Error Initializing Generic Rotation Abilities",
                    Function = "initAbilities()",
                    TheException = ex,
                }.Show();
            }
        }

        private bool CheckForLargeDifferences(Dictionary<Type, float> oldActs, Dictionary<Type, AbilityWrapper> abilWrappers, bool overunder)
        {
            foreach (Type t in oldActs.Keys)
            {
                if ((Math.Abs((overunder ? abilWrappers[t].NumActivatesO20 : abilWrappers[t].NumActivatesU20)
                    - oldActs[t]) > 0.1f)) { return true; }
            }
            return false;
        }

        public float SettleAll(float totalPercTimeLost, float resourceUsedByMaintenance, float availResourceO20, out float percFailResourceO20)
        {
            float percTimeInDPS = (1f - totalPercTimeLost);
            float percTimeInDPSAndO20 = percTimeInDPS * TimeOver20Perc;
            availResourceO20 -= resourceUsedByMaintenance * TimeOver20Perc;

            /* The following are dependant on other attacks as they are proc'g abilities or are the fallback item
             * We need to loop these until the activates are relatively unchanged
             * Heroic Strike, when there is Resource to do so, handled by the Heroic Strike class
             * Alternate to Cleave is MultiTargs is active, but only to the perc of time where Targs is active
             * After iterating how many Overrides can be done and still do other abilities, then do the white dps
             *
             * Starting Assumptions:
             * No ability ever procs so Slam sucks up all the cooldowns (except under <20% with that active, where Exec sucks all of them)
             * Heroic Strike and Cleave won't be used at all
             * Sudden Death Free Resource is minimum cost, no extra Resource available
             * Execute Free Resource is minimum cost, no extra Resource available
             * 
             * Hoped Ending Results:
             * All abilities will have proc'd and abilities that can proc from other ones will have their activates settled
             * Heroic Strikes and Cleave will activate when there's enough Resource to support them AND Executes
             * Sudden Death will get extra Resource leftovers if there are any
             * Execute will get extra Resource leftovers if there are any (since you won't use HS/CL <20%)
            */

            float preloopAvailGCDsO20 = GCDsAvailableO20, preloopAvailResourceO20 = availResourceO20;
            float origavailGCDsO20 = preloopAvailGCDsO20;
            Dictionary<Type, float> oldActs = new Dictionary<Type, float>();
            Dictionary<Type, float> abilSpace = new Dictionary<Type, float>();
            Dictionary<Type, AbilityWrapper> abilWrappers = new Dictionary<Type, AbilityWrapper>();

            oldActs[FillerMoveO20] = 0f; abilWrappers[FillerMoveO20] = GetWrapper(FillerMoveO20);
            foreach (Type t in StartingActionsOrder) { oldActs[t] = 0f; abilSpace[t] = 0f; abilWrappers[t] = GetWrapper(t); }
            foreach (Type t in RotationOrderO20) { oldActs[t] = 0f; abilSpace[t] = 0f; abilWrappers[t] = GetWrapper(t); }

            #region Opener
            // TODO 2012
            // GCDsAvailableO20 is an expensive operation, limit use
            float gcdsAvailableOpener = GCDsAvailableO20;
            float availResourceOpener = 0f;
            float actsOpener = 0;
            foreach (Type t in StartingActionsOrder)
            {
                if (abilWrappers[t].Ability.Validated && gcdsAvailableOpener > 0)
                {
                    preloopAvailGCDsO20 -= abilWrappers[t].Ability.GCDTime;
                    actsOpener = Math.Min(gcdsAvailableOpener, 1f);
                    abilWrappers[t].NumActivatesOpener = actsOpener * (1f - 0f);
                    availResourceOpener -= abilWrappers[t].ResourceOpener;
                    gcdsAvailableOpener -= abilWrappers[t].GCDUsageOpener;
                }
            }
            float openerTotalTime = origavailGCDsO20 - preloopAvailGCDsO20;
            foreach (AbilityWrapper aw in TheAbilityList)
            {
                aw.OpenerTotalTime = openerTotalTime;
            }
            #endregion

            abilWrappers[FillerMoveO20].NumActivatesO20 = preloopAvailGCDsO20;
            float origAvailResourceO20 = preloopAvailResourceO20 + availResourceOpener;
            abilWrappers[typeof(sith_warrior.Assault)].NumActivatesO20 = preloopAvailGCDsO20;
            availResourceO20 -= abilWrappers[typeof(sith_warrior.Assault)].ResourceO20;
            float repassAvailResourceO20 = 0f;
            percFailResourceO20 = 1f;

            int Iterator = 0;
            #region >20%
            while (Iterator < 50 && CheckForLargeDifferences(oldActs, abilWrappers, true))
            {
                // Store the previous values for CS and OP proc'g
                oldActs[FillerMoveO20] = abilWrappers[FillerMoveO20].NumActivatesO20;
                foreach (Type t in RotationOrderO20) { oldActs[t] = abilWrappers[t].NumActivatesO20; }
                // Set these all back to 0 so we can start fresh but factor the previous values where needed
                foreach (Type t in RotationOrderO20) { abilWrappers[t].NumActivatesO20 = 0f; }
                // Reset the Resource
                availResourceO20 = origAvailResourceO20;

                // Reset the Space values
                foreach (Type t in RotationOrderO20) { abilSpace[t] = 0f; }
                float acts = 0;

                // GCDsAvailableO20 is an expensive operation, trying to
                // see if I can speed it up by not calling it so much
                float gcdsAvailableO20 = GCDsAvailableO20;

                // ==== Primary Ability Priorities ====

                // Reduce abilities due to lack of Resource for maintaining the rotation
                if (repassAvailResourceO20 < 0f || percFailResourceO20 != 1f) {
                    // total the amount of resource you really need and turn it into a percentage that we failed
                    percFailResourceO20 *= 1f + repassAvailResourceO20 / (availResourceO20 - repassAvailResourceO20); // if repassAvailResource was -100 and availResource was 900, then this becomes 1 + (-100 / 900 - (-100)) = 1 - 100/1000 = 90%
                    if (percFailResourceO20 > 1f) { percFailResourceO20 = 1f; }
                    if (percFailResourceO20 <= 0f) { percFailResourceO20 = 0.10f; }
                } else { percFailResourceO20 = 1f; }

                foreach(Type t in RotationOrderO20)
                {
                    var aw = abilWrappers[t];
                    if (aw.Ability.Validated && gcdsAvailableO20 > 0)
                    {
                        acts = Math.Min(gcdsAvailableO20, aw.Ability.Activates * percTimeInDPSAndO20 * (aw.Ability.ResourceCost > 0f ? percFailResourceO20 : 1f));
                        aw.NumActivatesO20 = acts;// * (1f - OtherAbilspace);
                        if (aw.Ability.ResourceCost != 0) { availResourceO20 -= aw.ResourceO20 * ResourceModTotal; }
                        gcdsAvailableO20 -= aw.GCDUsageO20;
                    }
                    abilSpace[t] = aw.NumActivatesO20 / NumGCDsO20 * aw.Ability.UseTime / LatentGCD;
                }

                // Overpower
                // This is example code of an ability that can only be used as a proc of another ability.... update to a generic form
                /*if (OP.Ability.Validated && gcdsAvailableO20 > 0)
                {
                    float dodgesoverdurPluswhatwehaventprocessedyet = DodgedAttacksOverDur * TimeOver20Perc;
                    if (oldVRGCDs != 0) { dodgesoverdurPluswhatwehaventprocessedyet += oldVRGCDs * VR.Ability.AvgTargets * VR.Ability.SwingsPerActivate * VR.Ability.MHAtkTable.Dodge; }
                    if (oldHSGCDs != 0) { dodgesoverdurPluswhatwehaventprocessedyet += oldHSGCDs * HS.Ability.AvgTargets * HS.Ability.SwingsPerActivate * HS.Ability.MHAtkTable.Dodge; }
                    if (oldCLGCDs != 0) { dodgesoverdurPluswhatwehaventprocessedyet += oldCLGCDs * CL.Ability.AvgTargets * CL.Ability.SwingsPerActivate * CL.Ability.MHAtkTable.Dodge; }
                    if (oldSLGCDs != 0) { dodgesoverdurPluswhatwehaventprocessedyet += oldSLGCDs * SL.Ability.AvgTargets * SL.Ability.SwingsPerActivate * SL.Ability.MHAtkTable.Dodge; }
                    if (oldSoOActs != 0) { dodgesoverdurPluswhatwehaventprocessedyet += oldSoOActs * SoO.Ability.AvgTargets * SoO.Ability.SwingsPerActivate * SoO.Ability.MHAtkTable.Dodge; }
                    acts = Math.Min(gcdsAvailableO20, (OP.Ability as Overpower).GetActivates(dodgesoverdurPluswhatwehaventprocessedyet/*DodgedAttacksOverDur*//*, 0/*SoO.numActivatesO20*//*) * percTimeInDPSAndO20 * percFailResourceO20);
                    OP.NumActivatesO20 = acts * (1f - TFBspace - RDspace - BLSspace - MSspace);
                    availResourceO20 -= OP.ResourceO20 * ResourceModTotal * ResourceMOD_DeadlyCalm;
                    gcdsAvailableO20 -= OP.GCDUsageO20;
                }*/
                //OPspace = OP.numActivatesO20 / NumGCDsO20 * OP.ability.UseTime / LatentGCD;

                // Filler Move
                if (abilWrappers[FillerMoveO20].Ability.Validated && gcdsAvailableO20 > 0) {
                    acts = /*Math.Min(gcdsAvailableO20,*/ gcdsAvailableO20/*abilWrappers[FillerMove].Activates*/ * percTimeInDPS/*)*/;
                    abilWrappers[FillerMoveO20].NumActivatesO20 = acts;
                    availResourceO20 -= abilWrappers[FillerMoveO20].ResourceO20 * ResourceModTotal;
                    gcdsAvailableO20 -= abilWrappers[FillerMoveO20].GCDUsageO20;
                } else { abilWrappers[FillerMoveO20].NumActivatesO20 = 0f; }
                abilSpace[FillerMoveO20] = abilWrappers[FillerMoveO20].NumActivatesO20 / NumGCDsO20 * abilWrappers[FillerMoveO20].Ability.UseTime / LatentGCD;

                //float TotalSpace = (RDspace + THspace + BLSspace + MSspace + OPspace + TFBspace + CSspace + SLspace + HSspace + CLspace + VRspace);
                repassAvailResourceO20 = availResourceO20; // check for not enough resource to maintain rotation
                InvalidateCache();
                Iterator++;
            }
            #endregion

            _DPS_TTL_Opener = _TPS_TTL_Opener = _HPS_TTL_Opener = 0;
            _DPS_TTL_O20    = _TPS_TTL_O20    = _HPS_TTL_O20    = 0;
            float resourceNeededO20 = 0f, resourceGenOtherO20 = 0f;
            foreach (AbilityWrapper aw in TheAbilityList) {
                _DPS_TTL_Opener += aw.DPS_Opener;
                _TPS_TTL_Opener += aw.TPS_Opener;
                _HPS_TTL_Opener += aw.HPS_Opener;
                _DPS_TTL_O20 += aw.DPS_O20;
                _TPS_TTL_O20 += aw.TPS_O20;
                _HPS_TTL_O20 += aw.HPS_O20;
                if (aw.ResourceO20 > 0) { resourceNeededO20 += aw.ResourceO20; }
                else { resourceGenOtherO20 -= aw.ResourceO20; }
            }

            return 0f;
        }

        public float SettleAllU20(float totalPercTimeLost, float resourceUsedByMaintenance, float availResourceU20, out float percFailResourceU20)
        {
            float percTimeInDPS = (1f - totalPercTimeLost);
            float percTimeInDPSAndU20 = TimeUndr20Perc * percTimeInDPS;
            availResourceU20 -= resourceUsedByMaintenance * TimeUndr20Perc;

            /* The following are dependant on other attacks as they are proc'g abilities or are the fallback item
             * We need to loop these until the activates are relatively unchanged
             * Heroic Strike, when there is resource to do so, handled by the Heroic Strike class
             * After iterating how many Overrides can be done and still do other abilities, then do the white DPS
             *
             * Starting Assumptions:
             * No ability ever procs so Execute sucks up all the Cooldowns
             * Heroic Strike and Cleave won't be used at all
             * Execute FreeResource is 0, no extra resource available
             * 
             * Hoped Ending Results:
             * All abilities will have proc'd and abilities that can proc from other ones will have their activates settled
             * Execute will get extra resource leftovers if there are any (since you won't use HS/CL <20%)
             */

            float preloopAvailGCDsU20 = GCDsAvailableU20, preloopAvailResourceU20 = availResourceU20;
            float origavailGCDsU20 = preloopAvailGCDsU20;
            Dictionary<Type, float> oldActs = new Dictionary<Type, float>();
            Dictionary<Type, float> abilSpace = new Dictionary<Type, float>();
            Dictionary<Type, AbilityWrapper> abilWrappers = new Dictionary<Type, AbilityWrapper>();

            oldActs[FillerMoveU20] = 0f; abilWrappers[FillerMoveU20] = GetWrapper(FillerMoveU20);
            foreach (Type t in RotationOrderU20)
            {
                oldActs[t] = 0f; abilSpace[t] = 0f; abilWrappers[t] = GetWrapper(t);
            }

            abilWrappers[FillerMoveU20].NumActivatesU20 = preloopAvailGCDsU20;
            float origAvailResourceU20 = preloopAvailResourceU20;
            //availResourceU20 += abilWrappers[Assault].ResourceU20; // TODO 2012 FIX ME
            float repassAvailResourceU20 = 0f;
            percFailResourceU20 = 1f;

            /*
             * There's two major lines of reasoning here
             * - If Execute does more damage, use it instead of suchNsuch ability
             * - If the ability does more damage, use it instead (so that we can gradually shift
             *   from one rotation to the other based on rising gear levels)
             * - However, most of these abilities are only coming up so that we can do Taste for Blood.
             *   If an Overpower isn't doing as much damage as an Execute... there's no point to Rend,
             *   Thunderclap (refreshing rend), or TfB so we might as well turn them all *off* at once
             */

            int Iterator = 0;
            #region <20%
            while (Iterator < 50 && CheckForLargeDifferences(oldActs, abilWrappers, false))
            {
                // Store the previous values for CS and OP proc'g
                oldActs[FillerMoveU20] = abilWrappers[FillerMoveU20].NumActivatesU20;
                foreach (Type t in RotationOrderU20) { oldActs[t] = abilWrappers[t].NumActivatesU20; }
                // Set these all back to 0 so we can start fresh but factor the previous values where needed
                foreach (Type t in RotationOrderU20) { abilWrappers[t].NumActivatesU20 = 0f; }
                // Reset the Resource
                availResourceU20 = origAvailResourceU20;

                // Reset the Space values
                foreach (Type t in RotationOrderU20) { abilSpace[t] = 0f; }
                float acts = 0;

                // GCDsAvailableU20 is an expensive operation, trying to
                // see if I can speed it up by not calling it so much
                float gcdsAvailableU20 = GCDsAvailableU20;

                // ==== Primary Ability Priorities ====

                // Reduce abilities due to lack of Resource for maintaining the rotation
                if (repassAvailResourceU20 < 0f || percFailResourceU20 != 1f) {
                    // total the amount of Resource you really need and turn it into a percentage that we failed
                    percFailResourceU20 *= 1f + repassAvailResourceU20 / (availResourceU20 - repassAvailResourceU20); // if repassAvailResource was -100 and availResource was 900, then this becomes 1 + (-100 / 900 - (-100)) = 1 - 100/1000 = 90%
                    if (percFailResourceU20 > 1f) { percFailResourceU20 = 1f; }
                } else { percFailResourceU20 = 1f; }

                foreach (Type t in RotationOrderU20)
                {
                    if (abilWrappers[t].Ability.Validated && gcdsAvailableU20 > 0)
                    {
                        acts = Math.Min(gcdsAvailableU20, abilWrappers[t].Ability.Activates * percTimeInDPSAndU20 * percFailResourceU20);
                        abilWrappers[t].NumActivatesU20 = acts * (1f - 0f/*OtherAbilspace*/);
                        availResourceU20 -= abilWrappers[t].ResourceU20 * ResourceModTotal;
                        gcdsAvailableU20 -= abilWrappers[t].GCDUsageU20;
                    }
                    abilSpace[t] = abilWrappers[t].NumActivatesU20 / NumGCDsU20 * abilWrappers[t].Ability.UseTime / LatentGCD;
                }

                // Overpower
                // This is example code of an ability that can only be used as a proc of another ability.... update to a generic form
                /*if (OP.Ability.Validated && gcdsAvailableU20 > 0)
                {
                    float dodgesoverdurPluswhatwehaventprocessedyet = DodgedAttacksOverDur * TimeOver20Perc;
                    if (oldVRGCDs != 0) { dodgesoverdurPluswhatwehaventprocessedyet += oldVRGCDs * VR.Ability.AvgTargets * VR.Ability.SwingsPerActivate * VR.Ability.MHAtkTable.Dodge; }
                    if (oldHSGCDs != 0) { dodgesoverdurPluswhatwehaventprocessedyet += oldHSGCDs * HS.Ability.AvgTargets * HS.Ability.SwingsPerActivate * HS.Ability.MHAtkTable.Dodge; }
                    if (oldCLGCDs != 0) { dodgesoverdurPluswhatwehaventprocessedyet += oldCLGCDs * CL.Ability.AvgTargets * CL.Ability.SwingsPerActivate * CL.Ability.MHAtkTable.Dodge; }
                    if (oldSLGCDs != 0) { dodgesoverdurPluswhatwehaventprocessedyet += oldSLGCDs * SL.Ability.AvgTargets * SL.Ability.SwingsPerActivate * SL.Ability.MHAtkTable.Dodge; }
                    if (oldSoOActs != 0) { dodgesoverdurPluswhatwehaventprocessedyet += oldSoOActs * SoO.Ability.AvgTargets * SoO.Ability.SwingsPerActivate * SoO.Ability.MHAtkTable.Dodge; }
                    acts = Math.Min(gcdsAvailableU20, (OP.Ability as Overpower).GetActivates(dodgesoverdurPluswhatwehaventprocessedyet/*DodgedAttacksOverDur*//*, 0/*SoO.numActivatesU20*//*) * percTimeInDPSAndU20 * percFailResourceU20);
                    OP.NumActivatesU20 = acts * (1f - TFBspace - RDspace - BLSspace - MSspace);
                    availResourceU20 -= OP.ResourceU20 * ResourceModTotal * ResourceMOD_DeadlyCalm;
                    gcdsAvailableU20 -= OP.GCDUsageU20;
                }*/
                //OPspace = OP.numActivatesU20 / NumGCDsU20 * OP.ability.UseTime / LatentGCD;

                // Filler Move
                if (abilWrappers[FillerMoveU20].Ability.Validated && gcdsAvailableU20 > 0) {
                    acts = /*Math.Min(gcdsAvailableU20,*/ gcdsAvailableU20/*abilWrappers[FillerMove].Activates*/ * percTimeInDPS/*)*/;
                    abilWrappers[FillerMoveU20].NumActivatesU20 = acts;
                    availResourceU20 -= abilWrappers[FillerMoveU20].ResourceU20 * ResourceModTotal;
                    gcdsAvailableU20 -= abilWrappers[FillerMoveU20].GCDUsageU20;
                }
                else { abilWrappers[FillerMoveU20].NumActivatesU20 = 0f; }
                abilSpace[FillerMoveU20] = abilWrappers[FillerMoveU20].NumActivatesU20 / NumGCDsU20 * abilWrappers[FillerMoveU20].Ability.UseTime / LatentGCD;

                //float TotalSpace = (CSspace + RDspace + THspace + BLSspace /*+ MSspace*/ + OPspace + TFBspace /*+ SLspace*/ + EXspace /*+ HSspace + CLspace*/);
                repassAvailResourceU20 = availResourceU20; // check for not enough Resource to maintain rotation
                InvalidateCache();
                Iterator++;
            }
            #endregion

            float resourceNeededU20 = 0f, resourceGenOtherU20 = 0f;
            foreach (AbilityWrapper aw in TheAbilityList) {
                _DPS_TTL_U20 += aw.DPS_U20;
                _TPS_TTL_U20 += aw.TPS_U20;
                _HPS_TTL_U20 += aw.HPS_U20;
                if (aw.ResourceU20 > 0) { resourceNeededU20 += aw.ResourceU20; }
                else { resourceGenOtherU20 -= aw.ResourceU20; }
            }

            return 0f;
        }

        public void MakeRotationAndDoDPS(float percTimeUnder20) {
            if (CollChar.Char.MainHand == null) { return; }
            if (_needDisplayCalcs) { GCDUsage += string.Format("All=Opener+Over20%+Under20%. Only applicable if using Exec Spam\n{0:000.000}={1:000.000}+{2:000.000}+{3:000.000} : Total GCDs\n\n",
                NumGCDsAll, NumGCDsOpener, NumGCDsO20, NumGCDsU20); }

            // ==== Impedances ========================
            if (_needDisplayCalcs) { GCDUsage += "Impedances: Things that prevent you from DPS'g\n"; }
            float TotalPercTimeLost = CalculateTimeLost();
            if (_needDisplayCalcs && TotalPercTimeLost <= 0f) { GCDUsage += "None\n\n"; }
            else if (_needDisplayCalcs) { GCDUsage += (TotalPercTimeLost != 0f ? "\n" : ""); }

            // ==== Resource Generation Priorities ========
            float availResource = 0f;
            float PercFailResourceU20 = 1f, PercFailResourceO20 = 1f;
            availResource += ResourceGenOverDurOther + ResourceGainedWhileMoving;

            // ==== Maintenance and Anti-Impedance Priorities ====
            if (_needDisplayCalcs) GCDUsage += "Maintenance: Things that you do periodically to Buff yourself or the raid\n";
            float resourceUsedByMaintenance = DoMaintenanceActivates(TotalPercTimeLost);

            // ==== Standard Priorities ===============
            if (_needDisplayCalcs) GCDUsage += "Abilities: Things that you do to damage the Target. These are not in order of priority.\n";
            SettleAll(TotalPercTimeLost, resourceUsedByMaintenance, availResource, out PercFailResourceO20);
            if (percTimeUnder20 != 0f) { SettleAllU20(TotalPercTimeLost, resourceUsedByMaintenance, availResource, out PercFailResourceU20); }

            // Add each of the abilities' DPS and HPS values and other aesthetics
            if (_needDisplayCalcs) {
                if (PercFailResourceO20 != 1.0f || PercFailResourceU20 != 1.0f) {
                    GCDUsage += (PercFailResourceO20 < 1.0f ? string.Format("WARNING! You are losing {0:0.0%} of your abilities due\n" + "to Resource Starvation before Execute Rotation.\n", (1f - PercFailResourceO20)) : "");
                    GCDUsage += (PercFailResourceU20 < 1.0f ? string.Format("WARNING! You are losing {0:0.0%} of your abilities due\n" + "to Resource Starvation during Execute Rotation.\n", (1f - PercFailResourceU20)) : "");
                    GCDUsage += "\n";
                }
                List<AbilityWrapper> dmgAbils = DamagingAbilities;
                foreach (AbilityWrapper aw in dmgAbils) {
                    if (aw.AllNumActivates > 0 && !aw.Ability.IsMaint) {
                        if (aw.Ability.GCDTime < 1.5f || aw.Ability.GCDTime > 3f) {
                            // Overpower (And TfB procs) use less than a GCD to recuperate.
                            // Bladestorm is channeled over 6 seconds (approx 4 GCDs)
                            GCDUsage += string.Format("{0:000.000}=({1:000.000}+{2:000.000}+{3:000.000})@{4:0.000}s={5:000.000} : {6}\n",
                                aw.AllNumActivates, aw.NumActivatesOpener, aw.NumActivatesO20, aw.NumActivatesU20,
                                aw.Ability.GCDTime,
                                (aw.AllNumActivates * aw.Ability.GCDTime / (/*CollChar.CalcOpts.FullLatency +*/ 1.5f)),
                                aw.Ability.Name
                            );
                        } else {
                            GCDUsage += string.Format("{0:000.000}={1:000.000}+{2:000.000}+{3:000.000} : {4}{5}\n",
                                aw.AllNumActivates, aw.NumActivatesOpener, aw.NumActivatesO20, aw.NumActivatesU20,
                                aw.Ability.Name,
                                aw.Ability.UsesGCD ? "" : " (Doesn't use GCDs)");
                        }
                    }
                }
                GCDUsage += string.Format("\n{0:000.000}={1:000.000}+{2:000.000}+{3:000.000} : Available GCDs (should be at or near zero)",
                    GCDsAvailableOpener + GCDsAvailableO20 + (percTimeUnder20 != 0f ? GCDsAvailableU20 : 0f),
                    GCDsAvailableOpener,
                    GCDsAvailableO20,
                    (percTimeUnder20 != 0f ? GCDsAvailableU20 : 0f));
            }
        }

        public override void MakeRotationandDoDPS(bool needsDisplayCalculations) {
            try {
                base.MakeRotationandDoDPS(needsDisplayCalculations);
                MakeRotationAndDoDPS(TimeUndr20Perc);
            } catch (Exception ex) {
                new Base.ErrorBox()
                {
                    Title = "Error in creating Generic Rotation Details",
                    Function = "MakeRotationandDoDPS()",
                    TheException = ex,
                }.Show();
            }
        }
    }
}
